#include "Utils.h"
#include <getopt.h>

#define VERSION "1.0.10"
#define EXIT_NOW 1

typedef enum{
	false = 0,
	true
} t_bool;

struct globalArgs_t {
	int max_diff;  			/* -m option */
	char *out_file;			/* output file */
	const char *outFileName;	/* -o option */
	char **inputFiles;		/* input files */
	t_bool version;
	t_bool help;
	t_bool max;
} globalArgs;

static const char *optString = "vho:m:";

static const struct option longOpts[] = {
    { "version", no_argument, NULL, 'v' },
    { "help", no_argument, NULL, 'h' },
    { "output", required_argument, NULL, 'o' },
    { "max", required_argument, NULL, 'm' },
};

/**
 * Redimensiona linea para que ocupe 'tamanio' caracteres en memoria
 */
void extender_linea(char** linea, int tamanio) {
	int i;
	int tam_original = strlen(*linea);
	(*linea) = (char*) realloc (*linea, sizeof(char) * tamanio);

	/* Inicializo la nueva memoria */
	for (i = tam_original; i < tamanio; i++) {
		(*linea)[i] = 0;
	}
}

void comparar_linea_y_loggear(FILE* file_o, char** p_linea_1, char** p_linea_2, int nro_linea) {
	int tamanio;
	char *linea_1, *linea_2;
	int i, j, k;
	t_bool all_line = true, ninguna_diferencia = true;

	if (strlen(*p_linea_1) > strlen(*p_linea_2)) {
		tamanio = strlen(*p_linea_1);
		extender_linea(p_linea_2, tamanio);
	} else {
		tamanio = strlen(*p_linea_2);
		extender_linea(p_linea_1, tamanio);
	}

	linea_1 = *p_linea_1;
	linea_2 = *p_linea_2;

	for (i = 0; i < tamanio; i++) {
		if (linea_1[i] != linea_2[i]) {
			if (!all_line) {
				if (ninguna_diferencia) {
					fprintf(file_o, "line: %d - Cols:%d", nro_linea, i);
					ninguna_diferencia = false;
				} else {
					fprintf(file_o, ",%d", i);
				}
				if (--globalArgs.max_diff == 0) {
					break;
				}
			}
		} else if (all_line) {
			if ((linea_1[i] == '\n') && (linea_2[i] == '\n')) {
				if (i == 0) {
					all_line = false;
					i++;
				}
				break;
			}
			all_line = false;

			if (i != 0) {
				fprintf(file_o,"line: %d - Cols:0", nro_linea);
				ninguna_diferencia = false;
				if (--globalArgs.max_diff == 0) {
					break;
				}
			}

			for (j = 1; j < i; j++) {
				fprintf(file_o,",%d", j);
				if (--globalArgs.max_diff == 0) {
					break;
				}
			}
		}
	}

	if (all_line) {
		if (globalArgs.max == true){
			if (globalArgs.max_diff >= tamanio){
					
				fprintf(file_o,"line: %d - Cols:all line\n", nro_linea);
				globalArgs.max_diff=globalArgs.max_diff-tamanio;
			}
			else {
				fprintf(file_o,"line: %d - Cols:0", nro_linea);
				k=globalArgs.max_diff;
				for (globalArgs.max_diff=k-1;globalArgs.max_diff>0;globalArgs.max_diff--){
					fprintf(file_o,",%d", k-globalArgs.max_diff);
				}	
				fprintf(file_o,".\n");
			}
		}	
		else {
			fprintf(file_o,"line: %d - Cols:all line\n", nro_linea);
		}
	}

	if (!ninguna_diferencia) {
		fprintf(file_o, ".\n");
	}
}

/*
 * Imprime las opciones de uso en file_o
 */
void mostrar_ayuda (FILE* file_o){
	fprintf(stdout,
			"Usage:\n\n"
			"diff [options] from-file to-file\n"
			"diff - find differences between two files\n\n"
			"Options:\n"
			"  -v, --version\tPrint version and quit.\n"
			"  -h, --help\tPrint this information and quit.\n"
			"  -o, --output\tSpecifies the output file.\n"
			"  -m, --max\tShows the first m differences.\n");
}

/*
 * Imprime en file_o la version del programa
 */
void mostrar_version (FILE* file_o){
	fprintf(stdout,
			"diff (TP0 1er cuatrimestre 2011) " VERSION "\n");
}

/*
 * Imprime en file_o los numeros de linea del archivo file_in desde nro_linea hasta alcanzar el fin de archivo.
 */
void imprimir_lineas_sobrantes(FILE *file_in, FILE* file_o, char* file_name, int nro_linea) {
	char* linea;
	fprintf(file_o, "Solo en archivo %s: %d", file_name, nro_linea++);
	while (leerlinea(file_in, &linea) != EOF) {
		fprintf(file_o, ",%d", nro_linea++);
		free(linea);
	}
	fprintf(file_o, ".\n");
}

/**
 * Parsea las opciones que se reciben al invocar el programa
 */
int parsear_opciones(int argc, char* argv[], FILE **file_o) {
	int opt = 0;
	int longIndex = 0;

	opt = getopt_long( argc, argv, optString, longOpts, &longIndex );

	while( opt != -1 ) {
		switch( opt ) {
			case 'm':
				globalArgs.max_diff = atoi(optarg);
				globalArgs.max = true;	
				break;

			case 'o':
				globalArgs.outFileName = optarg;
				break;

			case 'v':
				globalArgs.version = true;
				break;

			case 'h':
				globalArgs.help = true;
				break;
			case '?':
				if (optopt == 'o' || 'm')

/*				else if (isprint (optopt))
					fprintf (stderr, "Opcion desconcoida `-%c'.\n", optopt);
				else
					fprintf (stderr,"Caracter de opcion Unknown `\\x%x'.\n", optopt);*/
				return EXIT_FAILURE;

			default:
				break;
		}

		opt = getopt_long( argc, argv, optString, longOpts, &longIndex );
	}	

	globalArgs.inputFiles = argv + optind;
	return EXIT_SUCCESS;
}

/**
 * Abre los archivos de entrada.
 * Devuelve EXIT_SUCCESS en caso de exito y EXIT_FAILURE si no los pudo abrir
 */
int open_files(FILE** file_1, FILE** file_2) {
	*file_1 = fopen(globalArgs.inputFiles[0], "rt");
	if ((*file_1) == NULL){
       return EXIT_FAILURE;
    }
	
	*file_2 = fopen(globalArgs.inputFiles[1], "rt");
	if ((*file_2) == NULL ){
		fclose(*file_1);
		return EXIT_FAILURE;
	}

	return EXIT_SUCCESS;
}

int open_output_file(FILE ** file_o){
	  if (globalArgs.outFileName == NULL){
		*file_o = stdout;
		}
	else {
		*file_o = fopen(globalArgs.outFileName, "wt");
		if (*file_o == NULL) {
			*file_o = stdout;
			return EXIT_FAILURE;
		}
	}
	return EXIT_SUCCESS;
}

/**
 * Inicializa el struct globalArgs
 */
void inicializar_arg() {
	globalArgs.max_diff = -1;
	globalArgs.out_file = NULL;
	globalArgs.inputFiles = NULL;
	globalArgs.outFileName = NULL;
	globalArgs.version = false;
	globalArgs.help = false;
	globalArgs.max = false;	
}

int main (int argc, char* argv[]) {
	FILE *file_1, *file_2, *file_o;
	char *linea_1, *linea_2;
	int return_file_1 = SUCCESS, return_file_2 = SUCCESS;
	int nro_linea = 0;
	
	
	
	inicializar_arg();

	if ( parsear_opciones(argc, argv, &file_o) ){
		return EXIT_FAILURE;
	}
	
	if (globalArgs.max_diff == 0) {
		return EXIT_SUCCESS;
	}
	
	if (globalArgs.help) {
		mostrar_ayuda(file_o);
		return EXIT_SUCCESS;
	}

	if (globalArgs.version) {
		mostrar_version(file_o);
		return EXIT_SUCCESS;
	}
	
	if ((argc - optind) != 2) {
		fprintf (stderr,"Error: Se deben especificar dos archivos para comparar\n");
		return EXIT_FAILURE;
	}

	if (open_files(&file_1, &file_2)) {
		fprintf (stderr,"Error: No se pudo de abrir el/los archivo/s de entrada\n");
		return EXIT_FAILURE;
	}

	if (open_output_file(&file_o)){
		fprintf (stderr,"Error: No se pudo de abrir el archivo de salida.\n");
		fclose(file_1);
		fclose(file_2);
		return EXIT_FAILURE;
	}	


	return_file_1 = leerlinea(file_1, &linea_1);
	return_file_2 = leerlinea(file_2, &linea_2);

	while ((return_file_1 != EOF) && (return_file_2 != EOF)) {

		comparar_linea_y_loggear(file_o, &linea_1, &linea_2, nro_linea);

		nro_linea++;
		free(linea_1);
		free(linea_2);
		if (globalArgs.max_diff == 0) break;

		return_file_1 = leerlinea(file_1, &linea_1);
		return_file_2 = leerlinea(file_2, &linea_2);
	}

	if ( !((return_file_1 == EOF) && (return_file_2 == EOF)) && globalArgs.max_diff != 0) {
		if (return_file_1 == EOF) {
			free(linea_2);
			imprimir_lineas_sobrantes(file_2, file_o, globalArgs.inputFiles[1], nro_linea);
		} else {
			free(linea_1);
			imprimir_lineas_sobrantes(file_1, file_o, globalArgs.inputFiles[0], nro_linea);
		}
	}

	fclose(file_1);
	fclose(file_2);
	return 0;
}
